Skip to content

Check the wire against BitRouter's published contract, and gate releases by hand - #7

Merged
SPIKESPIGEL404 merged 3 commits into
mainfrom
claude/contract-and-release-gates
Aug 24, 2026
Merged

Check the wire against BitRouter's published contract, and gate releases by hand#7
SPIKESPIGEL404 merged 3 commits into
mainfrom
claude/contract-and-release-gates

Conversation

@SPIKESPIGEL404

Copy link
Copy Markdown
Contributor

Two changes, both in service of one rule: a green pull request should be
safe to merge on its own, and the only step a person has to perform is
cutting a release.

The contract check

test/wire.test.ts proves the mapper reads a captured body correctly. It
cannot prove the capture still resembles what the server sends. That gap
is not hypothetical — this package once read context_window, a flat
cost, and a boolean reasoning, none of which BitRouter has ever sent,
so every model sat at its default window priced at zero and no test
minded.

BitRouter Cloud publishes its GET /v1/models response schema, generated
from the Rust types that serialize it, served unauthenticated at
/openapi.json. So there is nothing to guess at and no live API key in
CI. schema/models.schema.json is a vendored copy — vendored rather than
fetched, because the suite that gates merges has to be offline and
deterministic.

The load-bearing assertion is not "does the fixture parse". It is has
the set of fields BitRouter serves changed since somebody last looked
.
A new field fails once, and the fix is one line in ACKNOWLEDGED
recording whether it is mapped or ignored and why. A vanished field
fails louder, because that is the direction that goes quietly wrong.

It had already drifted. Production now serves hosted,
open_weights, latency, and throughput — three of them required —
and the fixtures predated all four. Recaptured verbatim here, with
claude-sonnet-5 added for the image_input path and grok-4.3 for
tiered pricing.

That last row documents a real gap rather than fixing it. A model can be
billed at a steeper rate once input crosses a threshold, for the whole
request — grok-4.3 doubles above 200k. toCost reports the base
bracket, so opencode understates a long prompt to a tiered model. An
opencode model entry carries one flat rate per direction and has nowhere
to put a ladder, so closing it needs an opencode-side change. Asserted
here so it stays known rather than being rediscovered.

contract.yml re-vendors the schema on a clock and opens a pull request
carrying the diff, so a contract change arrives as a reviewable change
with the suite's verdict already on it.

The release gate

publish.yml, triggered by a published release and deliberately not by a
push to main. That separation is what makes auto-merge safe rather than
reckless: main may move on its own because main reaches nobody, and
the step that does reach somebody stays manual. It re-runs the build, the
test typecheck, and the suite against the tagged tree, refuses a tag that
disagrees with package.json, publishes with provenance, and routes
prereleases to the next dist-tag.

Before merging

  • publish.yml needs an NPM_TOKEN secret and an npm environment.
  • contract.yml needs CONTRACT_PR_TOKEN. A pull request opened with
    the default GITHUB_TOKEN does not trigger workflows, which would
    leave it with no checks at all.

63 → 73 tests.

SPIKESPIGEL404 and others added 3 commits August 24, 2026 19:30
Publishing becomes a workflow of its own, triggered by a published
release and nothing else — deliberately not by a push to `main`.

That separation is the whole point. It lets `main` move on its own,
because `main` reaches nobody: a Dependabot bump or an automated
compatibility fix that goes green can merge itself. The step that does
reach somebody stays manual. Collapsing the two into a push trigger
would take the safety out of both.

It re-runs the build, the test typecheck, and the suite against the
tagged tree, refuses a tag that disagrees with package.json, publishes
with provenance, and sends prereleases to the `next` dist-tag.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The wire tests prove the mapper reads a captured body correctly. They
cannot prove the capture still resembles what the server sends, and a
stale fixture fails in the worst way available: silently. This package
once read `context_window`, a flat `cost`, and a boolean `reasoning`,
none of which BitRouter has ever sent, so every model sat at its
default window priced at zero and no test minded.

It turns out the contract is published. BitRouter Cloud generates an
OpenAPI document from the Rust types that serialize the response and
serves it unauthenticated, so there is nothing to guess at and no live
API key to hold in CI. `schema/models.schema.json` is a vendored copy
— vendored rather than fetched, because the suite that gates merges
has to be offline and deterministic.

The load-bearing assertion is not "does the fixture parse". It is
"has the set of fields BitRouter serves changed since somebody last
looked". A new field fails once, and the fix is one line in
ACKNOWLEDGED recording whether it is mapped or ignored and why. A
vanished field fails louder, because that is the direction that goes
quietly wrong.

Which had already happened: production now serves `hosted`,
`open_weights`, `latency`, and `throughput`, three of them required,
and the fixtures predated all four. They are recaptured verbatim here
— trimmed captures conform to nothing — with claude-sonnet-5 added
for the `image_input` path and grok-4.3 for tiered pricing.

That last one documents a real gap rather than fixing it. A model can
be billed at a steeper rate once input crosses a threshold, for the
whole request; `toCost` reports the base bracket, so opencode
understates a long prompt to a tiered model. An opencode model entry
carries one flat rate per direction and has nowhere to put a ladder,
so closing it needs an opencode-side change. Asserted so it stays
known.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The scheduled contract refresh hands this action a token with
contents:write and pull-requests:write, and a moving tag is a standing
invitation to whoever can move it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@SPIKESPIGEL404
SPIKESPIGEL404 merged commit d943fd8 into main Aug 24, 2026
1 check passed
@SPIKESPIGEL404
SPIKESPIGEL404 deleted the claude/contract-and-release-gates branch August 24, 2026 15:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant